/*!
    \copyright  Copyright (c) 2022 - 2024 Qualcomm Technologies International, Ltd.
                All Rights Reserved.
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file
    \ingroup    tmap_profile
    \brief      TMAP client sink
*/

#if defined(INCLUDE_LE_AUDIO_UNICAST) || defined(INCLUDE_LE_AUDIO_BROADCAST)

#include "tmap_client_sink.h"
#include "tmap_client_sink_private.h"
#include "bt_device.h"
#include "pairing.h"
#include "call_control_client.h"
#include "media_control_client.h"

#ifdef INCLUDE_LE_AUDIO_UNICAST

#endif

#define TMAP_LOG     DEBUG_LOG

#ifndef DISABLE_LE_AUDIO_VOICE
#define tmapClient_InitiateCallControlClientServiceDiscovery(cid)   CallControlClient_DiscoverServerService(cid)
#else
#define tmapClient_InitiateCallControlClientServiceDiscovery(cid)
#endif

#ifndef DISABLE_LE_AUDIO_MEDIA
#define tmapClient_InitiateMediaControlClientServiceDiscovery(cid)  MediaControlClient_DiscoverServerService(cid)
#else
#define tmapClient_InitiateMediaControlClientServiceDiscovery(cid)
#endif

/*! Profile connection is not allowed if it is already connected or max number of connection reached */
#define tmapClientSink_IsProfileConnectAllowed(cid)                                                     \
    (TmapClientSink_GetInstance(tmap_client_compare_by_cid, (unsigned) (cid)) == NULL &&                \
     TmapClientSink_GetInstance(tmap_client_compare_by_state, tmap_client_state_idle) != NULL)

/*! TMAP profile role is not known */
#define TMAP_CLIENT_ROLE_UNKNOWN    0

/*! Invalid handle for TMAP */
#define TMAP_CLIENT_INVALID_HANDLE (0xFFFF)

/*! \brief TMAP client task data. */
tmap_client_task_data_t tmap_taskdata;

/*! \brief Handler that receives notification from TMAP Library */
static void tmapClientSink_HandleMessage(Task task, MessageId id, Message message);

/*! \brief Get the stored handles from NVM. */
static void * tmapClientSink_RetrieveClientHandlesFromNvm(gatt_cid_t cid);

/*! \brief Write invalid TMAP client handles */
static void tmapClientSink_WriteInvalidClientHandles(gatt_cid_t cid);

/*! \brief Callback for checking service rediscovery is needed or not */
static bool tmapClientSink_IsServiceRediscoveryNeeded(gatt_cid_t cid);

/*! \brief Callbacks registered with gatt service discovery module */
static const gatt_service_discovery_callback_t tmap_client_sink_gatt_service_discovery_cb =
{
    .IsServiceRediscoveryNeeded = tmapClientSink_IsServiceRediscoveryNeeded,
};

/*! \brief Callback function to handle GATT Connect notification */
static void tmapClientSink_OnGattConnect(gatt_cid_t cid)
{
    UNUSED(cid);
}

/*! \brief Destroy tmap profile if any established for this connection */
UNITCODESECTION(KEEP)
static void tmapClientSink_OnGattDisconnect(gatt_cid_t cid)
{
    tmap_client_instance_t *instance = NULL;

    TMAP_LOG("tmapClientSink_OnGattDisconnect: cid=0x%04X", cid);

    instance = TmapClientSink_GetInstance(tmap_client_compare_by_cid, (unsigned)cid);
    if (instance != NULL)
    {
        if (instance->state == tmap_client_state_discovery)
        {
            /* During TMAP profile initialisation there could be a race condition - where LE ACL gets dropped before TMAP profile
             * initialization is successfull and TMAP profile intialization is still in progress. In such scenarios, TMAP client
             * should get removed before destroying the client instance.
             *
             * Since the instance is getting reset here, at the time of TMAP profile init confirmation with success status, there
             * shall not be a valid profile handle and CID in the instance. Hence remove the TMAP client device here.
             * 
             * Check if the profile handle is valid and remove the TMAP client device.
             */
             if (instance->tmap_profile_handle != 0)
             {
                 TmapClientRemoveDevice(instance->tmap_profile_handle, instance->cid);
             }
             
            /* Initialization is in progress Do not place a destroy request.Just reset the instance */
            TmapClientSink_ResetTmapClientInstance(instance);
        }
        else
        {
            /* If remote_db_changed flag has been set, it means that destroy request has already been placed */
            if (!instance->remote_db_changed)
            {
                TmapClientSink_DestroyInstance(cid);
            }
        }
    }
}

/*! \brief Connect to tmap profile */
UNITCODESECTION(KEEP)
static void tmapClientSink_ConnectProfile(gatt_cid_t cid)
{
    if (TmapClientSink_RetrieveClientHandles(cid) != NULL)
    {
        TMAP_LOG("tmapClientSink_ConnectProfile create instance cid 0x%x", cid);
        /* If handles are already there, it means we can directly create the instance */
        PanicFalse(TmapClientSink_CreateInstance(cid));
    }
    else
    {
        TMAP_LOG("tmapClientSink_ConnectProfile start find service range cid 0x%x", cid);
        GattServiceDiscoveryFindServiceRange(TrapToOxygenTask((Task)&tmap_taskdata.task_data),
                                             cid, GATT_SD_TMAS_SRVC);
    }
}

/*! \brief Handle encryption change indication */
UNITCODESECTION(KEEP)
static void tmapClientSink_OnEncryptionChanged(gatt_cid_t cid, bool encrypted)
{
    TMAP_LOG("tmapClientSink_OnEncryptionChanged: cid=0x%04X, encrypted:%d", cid, encrypted);

    /* If encryption change indication is from a handset and we received service discovery
     * completion message  */
    if (encrypted && BtDevice_IsDeviceHandsetOrLeHandset(GattConnect_GetBtLeDevice(cid)))
    {
        if (tmapClientSink_IsProfileConnectAllowed(cid) && GattServiceDisovery_IsServiceDiscoveryCompleted(cid))
        {
            /* Check if service discovery complete indication is received from gatt service discovery module */
            tmapClientSink_ConnectProfile(cid);
        }
    }
}

#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
/*! \brief Clear the stored handles from NVM */
static void tmapClientSink_ClearClientHandles(gatt_cid_t cid)
{
    device_t device = GattConnect_GetBtDevice(cid);

    if (device)
    {
        Device_RemoveProperty(device, device_property_tmap_client);
        DeviceDbSerialiser_SerialiseDevice(device);
    }
}

/*! \brief Handles remote database change indication */
static void tmapClientSink_OnRemoteDbChange(gatt_cid_t cid)
{
    tmap_client_instance_t *instance;

    /* Clear the stored client handles */
    tmapClientSink_ClearClientHandles(cid);

    instance = TmapClientSink_GetInstance(tmap_client_compare_by_cid, (unsigned)cid);

    if (instance == NULL)
    {
        /* This means we disconnected already. So just acknowledge */
        GattConnect_AcknowledgeRemoteDbChange(cid);
    }
    else
    {
        /* If state is in 'tmap_client_state_discovery', do not deinit the profile here. Instead
           wait for the initialization to get completed. */
        if (instance->state != tmap_client_state_discovery)
        {
            /* De-init the profile */
            TmapClientSink_DestroyInstance(cid);
        }

        /* Disconnect is in progress. Wait for to complete before acknowledging */
        instance->remote_db_changed = TRUE;
    }
}
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */

static const gatt_connect_observer_callback_t gatt_tmap_client_callback =
{
    .OnConnection = tmapClientSink_OnGattConnect,
    .OnDisconnection = tmapClientSink_OnGattDisconnect,
    .OnEncryptionChanged = tmapClientSink_OnEncryptionChanged
};

UNITCODESECTION(KEEP)
static pdd_size_t tmapClientSink_GetDeviceDataLength(device_t device)
{
    void *config = NULL;
    size_t config_size = 0;

    if (!Device_GetProperty(device, device_property_tmap_client, &config, &config_size))
    {
        config_size = 0;
    }
    return config_size;
}

UNITCODESECTION(KEEP)
static void tmapClientSink_SerialiseDeviceData(device_t device, void *buf, pdd_size_t offset)
{
    void *config = NULL;
    size_t config_size = 0;
    UNUSED(offset);

    if (Device_GetProperty(device, device_property_tmap_client, &config, &config_size))
    {
        memcpy(buf, config, config_size);
    }
}

UNITCODESECTION(KEEP)
static void tmapClientSink_DeserialiseDeviceData(device_t device, void *buf, pdd_size_t data_length, pdd_size_t offset)
{
    UNUSED(offset);

    Device_SetProperty(device, device_property_tmap_client, buf, data_length);
}

UNITCODESECTION(KEEP)
void TmapClientSink_RegisterAsPersistentDeviceDataUser(void)
{
    DeviceDbSerialiser_RegisterPersistentDeviceDataUser(
        PDDU_ID_LEA_TMAP_CLIENT,
        tmapClientSink_GetDeviceDataLength,
        tmapClientSink_SerialiseDeviceData,
        tmapClientSink_DeserialiseDeviceData);
}

/*! \brief Function that checks whether the TMAP matches based on the compare type */
UNITCODESECTION(KEEP)
static bool tmapClientSink_Compare(tmap_instance_compare_by_type_t type,
                                    unsigned compare_value,
                                    tmap_client_instance_t *instance)
{
    bool found = FALSE;

    switch (type)
    {
        case tmap_client_compare_by_cid:
            found = instance->cid == (gatt_cid_t) compare_value;
        break;

        case tmap_client_compare_by_state:
            found = instance->state == (tmap_client_state_t) compare_value;
        break;

        case tmap_client_compare_by_profile_handle:
            found = instance->tmap_profile_handle == (TmapClientProfileHandle) compare_value;
        break;

        case tmap_client_compare_by_bdaddr:
        {
            bdaddr addr;
            bdaddr *device_addr = (bdaddr *) compare_value;
            found = instance->state == tmap_client_state_connected &&
                    GattConnect_GetPublicAddrFromConnectionId(instance->cid, &addr) &&
                    BdaddrIsSame(&addr, device_addr);
        }
        break;

        case tmap_client_compare_by_valid_invalid_cid:
            found = instance->state == tmap_client_state_connected &&
               (instance->cid == (gatt_cid_t) compare_value || compare_value == INVALID_CID);
        break;

        default:
        break;
    }

    return found;
}

/*! \brief Get the TMAP client instance based on the compare type */
UNITCODESECTION(KEEP)
tmap_client_instance_t * TmapClientSink_GetInstance(tmap_instance_compare_by_type_t type, unsigned cmp_value)
{
    tmap_client_task_data_t *tmap_ctx = TmapClientSink_GetContext();
    tmap_client_instance_t *instance = NULL;

    ARRAY_FOREACH(instance, tmap_ctx->tmap_client_instance)
    {
    if (tmapClientSink_Compare(type, cmp_value, instance))
    {
        return instance;
    }
    }

    return NULL;
}

/*! \brief Function that checks whether the TMAS handles are already present in NVM */
UNITCODESECTION(KEEP)
static bool tmapClientSink_IsHandlesSameAsStoredData(GattTmasClientDeviceData *current_data,
                                                     unsigned gatt_cid)
{
    GattTmasClientDeviceData *stored_data = NULL;
    bool is_same_as_stored_data = FALSE;

    /* Get the TMAS handle information from NVM */
    stored_data = (GattTmasClientDeviceData*)tmapClientSink_RetrieveClientHandlesFromNvm(gatt_cid);

    if (stored_data != NULL &&
        (memcmp(stored_data, current_data, sizeof(GattTmasClientDeviceData)) == 0))
    {
        is_same_as_stored_data = TRUE;
    }

    return is_same_as_stored_data;
}

/*! \brief Write all the TMAP service handles to NVM */
UNITCODESECTION(KEEP)
static void tmapClientSink_WriteDeviceDataToStore(tmap_client_instance_t *instance)
{
    GattTmasClientDeviceData *dev_data = NULL;

    /* Retrieve all the discovered TMAS service handles */
    dev_data = TmapClientGetDevicedata(instance->tmap_profile_handle);

    /* Try to store the TMAS handle information in NVM */
    if (dev_data != NULL &&
        !tmapClientSink_IsHandlesSameAsStoredData(dev_data, instance->cid))
    {
        DEBUG_LOG("tmapClientSink_WriteDeviceDataToStore: Storing Handles in NVM");
        TmapClientSink_StoreClientHandles(instance->cid,
                                          (void*)dev_data,
                                          sizeof(GattTmasClientDeviceData));
    }

    /* Free the handle information */
    if (dev_data != NULL)
    {
        pfree(dev_data);
    }
}

/*! \brief Upon a successful initialization of TMAP, preserve the information in tmap client instance */
UNITCODESECTION(KEEP)
static void  tmapClientSink_HandleTmapProfileInitConfirmation(const TmapClientInitCfm *message)
{
    tmap_client_instance_t *instance = NULL;

    /* Try to find an existing instance with the same handle.
       If not found then get the next unused instance (based on handle is not set). */
    instance = TmapClientSink_GetInstance(tmap_client_compare_by_profile_handle, (unsigned)message->prflHndl);
    if (!instance)
    {
        instance = TmapClientSink_GetInstance(tmap_client_compare_by_profile_handle, (unsigned)0);
    }

    TMAP_LOG("tmapClientSink_HandleTmapProfileInitConfirmation prfl_handle: 0x%x, status: %d, instance: %p",
            message->prflHndl, message->status, instance);
            
    if (instance != NULL &&
        message->status == TMAP_CLIENT_STATUS_SUCCESS && 
        instance->tmap_profile_handle == message->prflHndl &&
        !instance->remote_db_changed)
    {
        instance->tmap_profile_handle = message->prflHndl;
        instance->state = tmap_client_state_connected;

        if (!instance->handover_in_progress)
        {
            TmapClientReadRoleReq(instance->tmap_profile_handle);
        }
        else
        {
            instance->handover_in_progress = FALSE;
        }

        /* Write TMAS handle information to Store */
        tmapClientSink_WriteDeviceDataToStore(instance);
    }
    else if (instance != NULL &&
             message->status == TMAP_CLIENT_STATUS_IN_PROGRESS &&
             instance->tmap_profile_handle == 0)
    {
        TMAP_LOG("tmapClientSink_HandleTmapProfileInitConfirmation setting profile handle 0x%x", message->prflHndl);
        instance->tmap_profile_handle = message->prflHndl;
    }
    else
    {
        if (message->status == TMAP_CLIENT_STATUS_SUCCESS &&
           (instance == NULL || instance->tmap_profile_handle != message->prflHndl))
        {
            /* Profile initialization is successful, but there could be below conditions:
             * 1. Instance is NULL, which indicates the GATT connection has already gone.
             *                             (OR)
             * 2. Instance is not NULL, but profile handles mismatch.This could happen
             *    if the init cfm has arrived very late, while we are already processing
             *    a new init request.Though a remote possibility, it can happen
             *
             *    Under both conditions place a destroy request immediately.
             */
            TMAP_LOG("tmapClientSink_HandleTmapProfileInitConfirmation destroying profile handle 0x%x", message->prflHndl);
            TmapClientDestroyReq(message->prflHndl);
        }
#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
        else if(instance != NULL && instance->remote_db_changed)
        {
            /* Remote database got changed while we initializing TMAP. */
            if (message->status == TMAP_CLIENT_STATUS_SUCCESS)
            {
                /* We should call TmapClientSink_DestroyInstance() here to ensure TmapClientRemoveDevice() getting
                   called. */
                TmapClientSink_DestroyInstance(instance->cid);
            }
            else
            {
                GattConnect_AcknowledgeRemoteDbChange(instance->cid);
                TmapClientSink_ResetTmapClientInstance(instance);
            }
        }
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */
        else
        {
            if (instance != NULL)
            {
                /* An error occurred during TMAP internal discovery. Reset the TMAP client instance */
                TMAP_LOG("tmapClientSink_HandleTmapProfileInitConfirmation resetting instance %p", instance);
                TmapClientSink_ResetTmapClientInstance(instance);
            }
        }
    }
}

/*! \brief Upon receiving a destroy confirmation, preserve the handles in NVM */
UNITCODESECTION(KEEP)
static void tmapClientSink_HandleTmapProfileDestroyConfirmation(const TmapClientDestroyCfm *message)
{
    tmap_client_instance_t *instance = NULL;

    TMAP_LOG("tmapClientSink_HandleTmapProfileDestroyConfirmation prfl_handle: 0x%x, status: %d",
            message->prflHndl, message->status);

    /* Find the matching call client instance based on the profile handle */
    instance = TmapClientSink_GetInstance(tmap_client_compare_by_profile_handle, (unsigned)message->prflHndl);

    if (instance != NULL &&
        message->status != TMAP_CLIENT_STATUS_IN_PROGRESS)
    {
#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
        /* Acknowledge if destroy is due to remote database change */
        if (instance->remote_db_changed)
        {
            GattConnect_AcknowledgeRemoteDbChange(instance->cid);
        }
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */

        /* Reset the TMAP client instance */
        TmapClientSink_ResetTmapClientInstance(instance);
    }
}

/* Handle TMAP role confirmation msg */
UNITCODESECTION(KEEP)
static void tmapClientSink_HandleTmapRoleConfirmation(const TmapClientRoleCfm* message)
{
    tmap_client_instance_t *instance = NULL;

    instance = TmapClientSink_GetInstance(tmap_client_compare_by_profile_handle, (unsigned)message->prflHndl);
    PanicNull(instance);

    if (message->status == TMAP_CLIENT_STATUS_SUCCESS)
    {
        TMAP_LOG("tmapClientSink_HandleTmapRoleConfirmation prfl_handle: 0x%x, instance: %p", message->prflHndl, instance);

        /* Initialise CCP */
        CallControlClient_SetServiceStatusForCid(instance->cid, (message->role & TMAP_ROLE_CALL_GATEWAY) == 0);

        /* Initialise MCP */
        MediaControlClient_SetServiceStatusForCid(instance->cid, (message->role & TMAP_ROLE_UNICAST_MEDIA_SENDER) == 0);
    }
}

/*! \brief Process notifications received from TMAP library */
UNITCODESECTION(KEEP)
static void tmapClientSink_HandleTmapMessage(Message message)
{
    CsrBtCmPrim tmap_id = *(CsrBtCmPrim *)message;

    switch (tmap_id)
    {
        case TMAP_CLIENT_INIT_CFM:
            tmapClientSink_HandleTmapProfileInitConfirmation((const TmapClientInitCfm*)message);
        break;

        case TMAP_CLIENT_DESTROY_CFM:
            tmapClientSink_HandleTmapProfileDestroyConfirmation((const TmapClientDestroyCfm*)message);
        break;

        case TMAP_CLIENT_ROLE_CFM:
            tmapClientSink_HandleTmapRoleConfirmation((const TmapClientRoleCfm*)message);
        break;

        default:
            TMAP_LOG("tmapClientSink_HandleTmapMessage Unhandled message id: 0x%x", (*(CsrBtCmPrim *)message));
        break;
    }
}

/*! \brief Create the TMAP Client Instance */
UNITCODESECTION(KEEP)
bool TmapClientSink_CreateInstance(gatt_cid_t cid)
{
    tmap_client_instance_t *instance = NULL;

    instance = TmapClientSink_GetInstance(tmap_client_compare_by_state, tmap_client_state_idle);
    if (instance != NULL)
    {
        instance->cid = cid;
        instance->state = tmap_client_state_discovery;

#ifdef ENABLE_TMAP_PROFILE
        TmapClientHandles tmap_handle_data = {0};

        tmap_handle_data.tmasClientHandle = (GattTmasClientDeviceData*)TmapClientSink_RetrieveClientHandles(cid);

        TmapClientInitData client_init_params;
        client_init_params.cid = cid;
        TmapClientInitReq(TrapToOxygenTask((Task)&tmap_taskdata.task_data),
                          &client_init_params,
                          tmap_handle_data.tmasClientHandle == NULL ? NULL : &tmap_handle_data);
#endif
        return TRUE;
    }

    return FALSE;
}

/*! \brief If TMAS Service is discovered successfully, initialize the Call control profile */
UNITCODESECTION(KEEP)
static void tmapClientSink_HandleServiceRange(const GATT_SERVICE_DISCOVERY_FIND_SERVICE_RANGE_CFM_T *msg)
{
    bool tmas_service_present = FALSE;

    TMAP_LOG("tmapClientSink_HandleServiceRange Number of LEA Services cid: 0x%x, result: %d, Found: %d",
            msg->cid, msg->result, msg->srvcInfoCount);

    if (msg->result == GATT_SD_RESULT_SUCCESS &&
        msg->srvcInfoCount !=0 &&
        msg->srvcInfo->srvcId & GATT_SD_TMAS_SRVC)
    {
        PanicFalse(TmapClientSink_CreateInstance(msg->cid));
        pfree(msg->srvcInfo);

        tmas_service_present = TRUE;
    }
    else
    {
        TMAP_LOG("tmapClientSink_HandleServiceRange unique TMAS Service not found in Remote Server cid: 0x%x", msg->cid);

        CallControlClient_SetServiceStatusForCid(msg->cid, FALSE);
        MediaControlClient_SetServiceStatusForCid(msg->cid, FALSE);
    }

    if (!tmas_service_present &&
        (msg->result == GATT_SD_RESULT_SUCCESS || msg->result == GATT_SD_RESULT_SRVC_ID_NOT_FOUND))
    {
        /* TMAS is not present in remote server. Write invalid handles to NVM so that we don't trigger unnecessary service
           rediscovery on tmapClientSink_IsServiceRediscoveryNeeded(). */
        tmapClientSink_WriteInvalidClientHandles(msg->cid);
    }
}

/*! \brief Handler to handle GATT Service Discovery related primitives */
UNITCODESECTION(KEEP)
static void tmapClientSink_HandleGattPrim(Message message)
{
    switch (*(CsrBtCmPrim *)message)
    {
        case GATT_SERVICE_DISCOVERY_FIND_SERVICE_RANGE_CFM:
            tmapClientSink_HandleServiceRange((const GATT_SERVICE_DISCOVERY_FIND_SERVICE_RANGE_CFM_T*)message);
        break;

        default:
        break;
    }
}

/*! \brief Check if the TMAS service is available in remote server and is discovered */
UNITCODESECTION(KEEP)
static void tmapClientSink_HandleGattDiscoveryComplete(const GATT_SERVICE_DISCOVERY_COMPLETE_T *ind)
{
    TMAP_LOG("tmapClientSink_HandleGattDiscoveryComplete cid: 0x%x", ind->cid);

    /* Need a check to see if TMAP is supported?
       No, that is done later. GATT_SERVICE_DISCOVERY_COMPLETE is only
       telling us that the GATT service discovery process is complete. */

    if (tmapClientSink_IsProfileConnectAllowed(ind->cid) && GattConnect_IsEncrypted(ind->cid))
    {
        TMAP_LOG("tmapClientSink_HandleGattDiscoveryComplete connect profile");

        /* If link is encrypted already, we can connect to tmap profiles immediately */
        tmapClientSink_ConnectProfile(ind->cid);
    }
}

/*! \brief Process notifications received for TMAP client task */
UNITCODESECTION(KEEP)
static void tmapClientSink_HandleMessage(Task task, MessageId id, Message message)
{
    UNUSED(task);
    TMAP_LOG("tmapClientSink_HandleMessage Received Message Id : 0x%x", id);

    switch (id)
    {
        case GATT_SERVICE_DISCOVERY_COMPLETE:
            tmapClientSink_HandleGattDiscoveryComplete((const GATT_SERVICE_DISCOVERY_COMPLETE_T*)message);
        break;

        case GATT_SD_PRIM:
            tmapClientSink_HandleGattPrim(message);
        break;

        case TMAP_CLIENT_PROFILE_PRIM:
            tmapClientSink_HandleTmapMessage(message);
        break;

        default:
        break;
    }
}

/*! \brief Callback for checking service rediscovery is needed or not */
UNITCODESECTION(KEEP)
static bool tmapClientSink_IsServiceRediscoveryNeeded(gatt_cid_t cid)
{
    /* If there is no client handles in NVM, then that is an indication that TMAS service discovery
       is not yet initiated. So request for rediscovery in that case.
       Note that if TMAS is not in remote server, then the client handles will not be NULL (it will be
       invalid though) and we will not request for re-discovery. */
    return tmapClientSink_RetrieveClientHandlesFromNvm(cid) == NULL;
}

/*! \brief Register with GATT LEA Service discovery */
UNITCODESECTION(KEEP)
bool TmapClientSink_Init(Task init_task)
{
    tmap_client_instance_t *instance = NULL;

    UNUSED(init_task);

    TMAP_LOG("TmapClientSink_Init");

    memset(&tmap_taskdata, 0, sizeof(tmap_taskdata));
    tmap_taskdata.task_data.handler = tmapClientSink_HandleMessage;

    ARRAY_FOREACH(instance, tmap_taskdata.tmap_client_instance)
    {
        TmapClientSink_ResetTmapClientInstance(instance);
    }

    GattServiceDiscovery_ClientRegister(&tmap_taskdata.task_data);
    GattConnect_RegisterObserver(&gatt_tmap_client_callback);
    GattServiceDiscovery_RegisterForCallback(&tmap_client_sink_gatt_service_discovery_cb);
#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
    GattConnect_RegisterRemoteDbChangeCallback(tmapClientSink_OnRemoteDbChange);
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */

    return TRUE;
}

/*! \brief Write invalid TMAP handles to NVM to indicate that TMAP is not discovered in remote server. */
UNITCODESECTION(KEEP)
static void tmapClientSink_WriteInvalidClientHandles(gatt_cid_t cid)
{
    GattTmasClientDeviceData dev_data = {.startHandle = TMAP_CLIENT_INVALID_HANDLE,
                                         .endHandle = TMAP_CLIENT_INVALID_HANDLE};

    /* Store the invalid TMAS handle information in NVM */
    if (!tmapClientSink_IsHandlesSameAsStoredData(&dev_data, cid))
    {
        DEBUG_LOG("tmapClientSink_WriteInvalidClientHandles cid 0x%x", cid);
        TmapClientSink_StoreClientHandles(cid,
                                          (void*)&dev_data,
                                          sizeof(GattTmasClientDeviceData));
    }
}

/*! \brief Get the stored handles from NVM. It could be either
           a) valid handles if TMAP handles discovered before
           b) Invalid handles if TMAS is not found in remote server
           c) NULL if we have not yet triggered for TMAP handle discovery
 */
UNITCODESECTION(KEEP)
static void * tmapClientSink_RetrieveClientHandlesFromNvm(gatt_cid_t cid)
{
    device_t device = GattConnect_GetBtDevice(cid);
    void *server_handle_info = NULL;

    if (device)
    {
        size_t size;

        if (!Device_GetProperty(device, device_property_tmap_client, &server_handle_info, &size))
        {
            server_handle_info = NULL;
        }
    }

    DEBUG_LOG("tmapClientSink_RetrieveClientHandlesFromNvm %p", server_handle_info);

    return server_handle_info;
}

/*! \brief Method used to retrieve discovered TMAS handles data from NVM */
UNITCODESECTION(KEEP)
void * TmapClientSink_RetrieveClientHandles(gatt_cid_t cid)
{
    GattTmasClientDeviceData *stored_data = tmapClientSink_RetrieveClientHandlesFromNvm(cid);

    if (stored_data != NULL &&
       (stored_data->startHandle != TMAP_CLIENT_INVALID_HANDLE || stored_data->endHandle != TMAP_CLIENT_INVALID_HANDLE))
    {
        /* Return handles only if they are valid */
        return stored_data;
    }

    return NULL;
}

/*! \brief Method used to store discovered TMAS handles data to NVM */
UNITCODESECTION(KEEP)
bool TmapClientSink_StoreClientHandles(gatt_cid_t cid, void *config, uint8 size)
{
    bool handles_written = FALSE;
    device_t device = GattConnect_GetBtDevice(cid);

    if (device)
    {
        Device_SetProperty(device, device_property_tmap_client, config, size);
        DeviceDbSerialiser_SerialiseDevice(device);
        handles_written = TRUE;
    }

    return handles_written;
}

/*! \brief Reset the provided TMAP client instance */
UNITCODESECTION(KEEP)
void TmapClientSink_ResetTmapClientInstance(tmap_client_instance_t *tmap_client)
{
    if (tmap_client)
    {
        memset(tmap_client, 0, sizeof(tmap_client_instance_t));

        tmap_client->cid = INVALID_CID;
        tmap_client->state = tmap_client_state_idle;
        tmap_client->tmap_profile_handle = 0;
    }
}

/*! \brief Read the TMAP role characteristics */
UNITCODESECTION(KEEP)
void TmapClientSink_ReadTmapRole(gatt_cid_t cid)
{
    tmap_client_instance_t *instance = NULL;

    instance = TmapClientSink_GetInstance(tmap_client_compare_by_valid_invalid_cid, (unsigned)cid);
    if (instance)
    {
        TmapClientReadRoleReq(instance->tmap_profile_handle);
    }
}

/*! \brief Check If TMAP is connected or not */
UNITCODESECTION(KEEP)
bool TmapClientSink_IsTmapConnected(void)
{
    return TmapClientSink_GetInstance(tmap_client_compare_by_valid_invalid_cid, (unsigned)INVALID_CID) != NULL;
}

UNITCODESECTION(KEEP)
bool TmapClientSink_DestroyInstance(gatt_cid_t cid)
{
    tmap_client_instance_t *instance = NULL;

    TMAP_LOG("TmapClientSink_DestroyInstance: cid=0x%04X", cid);

    instance = TmapClientSink_GetInstance(tmap_client_compare_by_cid, (unsigned)cid);
    if (instance)
    {
        /* Before destroying, needs to call TmapClientRemoveDevice() to remove any active devices */
        TmapClientRemoveDevice(instance->tmap_profile_handle, cid);
        TmapClientDestroyReq(instance->tmap_profile_handle);

        return TRUE;
    }

    return FALSE;
}

#endif /* defined(INCLUDE_LE_AUDIO_UNICAST) || defined(INCLUDE_LE_AUDIO_BROADCAST) */
